package org.rsbot.script.wrappers;

import java.awt.Point;

import org.rsbot.bot.Bot;
import org.rsbot.client.Model;
import org.rsbot.client.Node;
import org.rsbot.client.RSNPCNode;
import org.rsbot.script.Calculations;
import org.rsbot.script.Constants;
import org.rsbot.script.InputManager;
import org.rsbot.script.Methods;

public abstract class RSCharacter {

    public final Bot bot;
	public final InputManager input;
	public final Methods methods;
	
	public static enum Orientation {
		North (8192), NorthEast (10240), East (12288), SouthEast (14336), South (0), SouthWest (2048), West (4096), NorthWest (6144);
		
		private int orientation;
		private Orientation(final int orientation) {
			this.orientation = orientation;
		}
		
		public int getOrientationId() {
			return orientation;
		}
		
		public static Orientation getOrientation(final int intor) {
			for (final Orientation or : values()) {
				if (or.getOrientationId() == intor)
					return or;
			}
			return null;
		}
	}
	
	public abstract org.rsbot.client.RSCharacter getAccessor();
	

	public RSCharacter(final Bot bot) {
		this.input = bot.getInputManager();
		this.methods = bot.methods;
		this.bot = bot;
	}

	/**
	 * @inheritDoc java/lang/Object#equals(java/lang/Object)
	 */
	@Override
	public boolean equals(final Object obj) {
		if (obj == this)
            return true;
		if (obj instanceof org.rsbot.script.wrappers.RSCharacter) {
			final org.rsbot.script.wrappers.RSCharacter cha = (org.rsbot.script.wrappers.RSCharacter) obj;
			return cha.getAccessor() == getAccessor();
		}
		return false;
	}

	/**
	 * @return animation of character as integer
	 */
	public int getAnimation() {
		return getAccessor().getAnimation();
	}

	public int getGraphic() {
		return getAccessor().getGraphicsData()[0].getID();
	}

	public int getHeight() {
		return getAccessor().getHeight();
	}
	
	/**
	 * @return Orientation aka turn direction
	 */
	public Orientation getOrientation() {
		return Orientation.getOrientation(getAccessor().getOrientation());
	}

	/**
	 * Returns the % of HP. Returns 100 if not in combat.
	 */
	public int getHPPercent() {
		return isInCombat() ? getAccessor().getHPRatio() * 100 / 255 : 100;
	}

	public RSCharacter getInteracting() {

		final int interact = getAccessor().getInteracting();
		if (interact == -1)
            return null;

		if (interact < 32768) {
			final Node localNode = bot.calculations.findNodeByID(bot.getClient()
					.getRSNPCNC(), interact);
			if (localNode == null || !(localNode instanceof RSNPCNode))
                return null;
			return new RSNPC(((RSNPCNode) localNode).getRSNPC(), bot);
		} else if (interact >= 32768) {
			int index = interact - 32768;
			if (index == bot.getClient().getSelfInteracting()) {
				index = 2047;
			}

			return new org.rsbot.script.wrappers.RSPlayer(bot.getClient()
					.getRSPlayerArray()[index], bot);
		}

		return null;
	}

	public RSTile getLocation() {
		if (getAccessor() == null)
            return new RSTile(-1, -1);
		final int x = bot.getClient().getBaseX() + (getAccessor().getX() >> 9);
		final int y = bot.getClient().getBaseY() + (getAccessor().getY() >> 9);
		return new RSTile(x, y);
	}

	public String getMessage() {
		return getAccessor().getMessage();
	}

	/**
	 * Get's the minimap location, of the character. Note: This does work when
	 * it's walking!
	 * 
	 * @return The location of the character on the minimap.
	 */
	public Point getMinimapLocation() {
		final RSTile t = getLocation();

		/*
		 * final int cX = bot.getClient().getBaseX() + (getAccessor().getX() / 32 - 2) / 4;
		 * final int cY = bot.getClient().getBaseY() + (getAccessor().getY() / 32 - 2) / 4;
		 */
		return methods.calc.worldToMinimap(t.getX(), t.getY());
	}

	/**
	 * Character model if available.
	 * 
	 * @return <b>RSModel</b> if available else null
	 */
	public RSModel getModel() {
		if (getAccessor() != null) {
			final Model model = getAccessor().getModel();
			if (model != null)
                return new RSCharacterModel(methods, model, getAccessor());
		}
		return null;
	}

	/**
	 * @Deprecated Returns "UNDEFINED"
	 */
	@Deprecated
	public String getName() {
		return "UNDEFINED"; // should be overridden, obviously
	}

	/**
	 * @Deprecated Returns -1
	 */
	@Deprecated
	public int getLevel() {
		return -1; // should be overridden as well
	}

	/**
	 * Character point on screen.
	 * 
	 * @return <b>Point</b> on screen, if not on screen null
	 */
	public Point getScreenLocation() {
		/*
		 * RSTile loc = getLocation(); // return
		 * Calculations.tileToScreen(logetAccessor().getX(), logetAccessor().getY(), // 0.5, 0.9,
		 * (getAccessor().getHeight() / 2));
		 */
		return methods.calc.worldToScreen(getAccessor().getX(), getAccessor().getY(),
				-getAccessor().getHeight() / 2);
	}

	public int getTurnDirection() {
		return getAccessor().getOrientation();
	}

	@Override
	public int hashCode() {
		return System.identityHashCode(getAccessor());
	}

	/**
	 * Checks to see if the Player is in Combat
	 * 
	 * @return <tt>True</tt> if is in combat, otherwise <tt>False</tt>
	 */
	public boolean isInCombat() {
		if (!methods.isLoggedIn())
            return false;
		return methods.isLoggedIn()
				&& bot.getClient().getLoopCycle() < getAccessor().getLoopCycleStatus();
	}

	/**
	 * Checks to see if the Player is Poisoned
	 * 
	 * @return <tt>True</tt> if is poisoned, otherwise <tt>False</tt>
	 */
	public boolean isPoisoned() {
		if (!methods.isLoggedIn())
            return false;
		return methods.isLoggedIn() && methods.getSetting(102) > 0
				|| methods.getInterface(748, 4).getBackgroundColor() == 1801;
	}

	public boolean isInteractingWithLocalPlayer() {
		return getAccessor().getInteracting() - 32768 == bot.getClient()
				.getSelfInteracting();
	}

	/**
	 * Checks if character is moving.
	 * 
	 * @return <tt>true</tt> if moving, <tt>false</tt> otherwise
	 */
	public boolean isMoving() {
		return getAccessor().isMoving() != 0;
	}

	/**
	 * Checks if character is on screen.
	 * 
	 * @return <tt>true</tt> if on screen, <tt>false</tt> otherwise
	 */
	public boolean isOnScreen() {
		final Point p = methods.calc.tileToScreen(this.getLocation());
		return p.getX() > 0 && p.getY() > 0;
	}

	public boolean isValid() {
		return getAccessor() != null;
	}

	@Override
	public String toString() {
		final RSCharacter inter = getInteracting();
		return "[anim="
				+ getAnimation()
				+ ",msg="
				+ getMessage()
				+ ",interact="
				+ (inter == null ? "null" : inter.isValid() ? inter
						.getMessage() : "Invalid") + "]";
	}

	/**
	 * Performs an action on the humanoid character instance (tall and skinny)
	 * without any randomly generated mousepaths.
	 * 
	 * @param option
	 *            The option to be clicked (If available).
	 * @return <tt>true</tt> if the option was found; otherwise <tt>false</tt>.
	 * @see #do(RSNPC, String, boolean)
	 */
	public boolean action(final String option) {
		return this.action(option, false);
	}

        /**
	 * Performs an action on a humanoid character (tall and skinny).
	 *
	 * @param action The action of the menu entry to be clicked (if available).
	 * @param option The option of the menu entry to be clicked (if available).
	 * @return <tt>true</tt> if the option was found; otherwise <tt>false</tt>.
	 */
	public boolean action(final String action, final String option) {
		final RSModel model = getModel();
		return model != null && isValid() && getModel().action(action, option);
	}

	/**
	 * Performs an action on the humanoid character instance (tall and skinny).
	 * 
	 * @param option
	 *            The option to be clicked (If available). If null, simply left
	 *            clicks the NPC.
	 * @param mousepath
	 *            Whether or not to use {@link #moveMouseByPath(Point)} rather
	 *            than {@link #moveMouse(Point)}.
	 * @return <tt>true</tt> if the option was found; otherwise <tt>false</tt>.
	 * @see #moveMouseByPath(Point)
	 * @see #atMenu(String)
	 */
	public boolean action(final String option, final boolean mousepath) {
		for (int i = 0; i < 10; i++) {
			if (!methods.pointOnScreen(this.getScreenLocation())
					|| !this.isOnScreen())
                return false;

			if (!mousepath) {
				methods.moveMouse(new Point((int) Math.round(this
						.getScreenLocation().getX()) + methods.random(-5, 5),
						(int) Math.round(this.getScreenLocation().getY())
								+ methods.random(-5, 5)));
			} else {
				methods.moveMouseByPath(new Point((int) Math.round(this
						.getScreenLocation().getX()) + methods.random(-5, 5),
						(int) Math.round(this.getScreenLocation().getY())
								+ methods.random(-5, 5)));
			}

			if (option == null) {
				input.clickMouse(true);
				return true;
			} else {
				if (methods.atMenu(option))
                    return true;
			}
		}

		return false;
	}

	/**
	 * Obtains the distance in tiles from the current player to the character.
	 * 
	 * @return Distance in tiles from the current player to the character.
	 */
	public int distanceTo() {
	    return methods.calc.distanceBetween(methods.getLocation(), this.getLocation());
	}

	/**
	 * Checks if character is on minimap.
	 * 
	 * @return <tt>true</tt> if on minimap, <tt>false</tt> otherwise
	 */
	public boolean isOnMinimap() {
		final RSTile loc = this.getLocation();
		return (loc != null);
	}

	/**
	 * Returns the angle to this character
	 * 
	 * @return The angle
	 */
	public int getAngle() {
		final RSTile loc = this.getLocation();
		return methods.getAngleToCoordinates(loc.getX(), loc.getY());
	}

	/**
	 * Turns to the character with a random deviation of 2 degrees
	 */
	public void turnTo() {
		final int angle = this.getAngle() + methods.random(-2, 2);
		methods.setCameraRotation(angle);
	}

	/**
	 * Uses the specified inventory item on this character.
	 * 
	 * @param item
	 * @return <tt>true</tt> if successful, <tt>false</tt> otherwise
	 */
	public boolean useItemOn(final RSItem item) {
		if (methods.getCurrentTab() != Constants.TAB_INVENTORY) {
			methods.openTab(Constants.TAB_INVENTORY);
		}

		return methods.atInventoryItem(item.getID(), "Use") && this.click(true);
	}

	/**
	 * Left or Right clicks on this character.
	 * 
	 * @param leftClick
	 *            <tt>true</tt> for left click, <tt>false</tt> for right click.
	 * @return <tt>true</tt> if successful, <tt>false</tt> otherwise.
	 */
	public boolean click(final boolean leftClick) {
		return this.action(null);
	}
}
